create or replace package body m4o_output_advice as

  C_MODULE constant m4o_module := new m4o_module('m4o_output_advice');

  -- ===========================================================================
  procedure apply_before_body_part(
    i_part in out nocopy m4o_package_generator.part_t)
  is
    v_pc m4o_context := C_MODULE.proc('apply_before_body_part');
    v_generator         generator_t;
    v_escape_fn         fn_t;
    v_linestart_pattern varchar2(4000);
    -- -------------------------------------------------------------------------
    function un_embed(i_block in varchar2)
    return varchar2
    is
      v_startpos      pls_integer;
      v_tag_start_pos pls_integer;
      v_tag_end_pos   pls_integer;
      v_block         m4o_v2.t;
      v_output        m4o_v2.t;
      v_tag_code      m4o_v2.t;
    begin
      --
      -- remove linestarts
      --
      v_block := regexp_replace(
                   i_block,
                   v_linestart_pattern,
                   null,
                   1,
                   0,
                   'm');

      v_startpos := 1;
      loop
        v_tag_start_pos := instr(v_block,
                             '<%',
                             v_startpos);
        if nvl(v_tag_start_pos,0) = 0 then
          --
          -- no embedded tag found anymore
          --
          if v_tag_start_pos <= length(v_block) then
            if v_output is not null then
              v_output := v_output||'||';
            end if;
            v_output := v_output||
                        ''''||
                        replace(substr(v_block,
                                       v_startpos),
                                '''',
                                '''''')||
                        '''';
          end if;
          --
          -- EXIT LOOP
          --
          exit;
        end if;

        if v_tag_start_pos > v_startpos then
          --
          -- copy chars between start and tag pos
          --
          if v_output is not null then
            v_output := v_output||'||';
          end if;
          v_output := v_output||
                      ''''||
                      replace(substr(v_block,
                                     v_startpos,
                                     v_tag_start_pos-v_startpos),
                              '''',
                              '''''')||
                      '''';
        end if;

        --
        -- find tag end and emit the code in between
        -- if the code starts with "%" (i.e. the tag start was "<%%",
        -- the escape function is ignored.
        -- otherwise, if an escape function is defined, the code is
        -- filtered through it.
        -- i.e. for an escape function of sys.htf.escape_sc and htp
        -- output, the following code
        --
        --   %<td><%v_val1%></td>
        --   %<td><%%v_val2%></td>
        --
        -- generates
        --
        --   sys.htp.p('<td>'||sys.htf.escape_sc(v_val1)||'</td>
        -- <td>'||v_val2||'</td>');
        --

        v_tag_end_pos := instr(v_block,
                               '%>',
                               v_tag_start_pos);
        if v_tag_end_pos = 0 then
          v_pc.error('Missing %>');
        else
          if v_output is not null then
            v_output := v_output||'||';
          end if;
          v_tag_code := substr(v_block,
                               v_tag_start_pos+2,
                               v_tag_end_pos-v_tag_start_pos-2);
          if substr(v_tag_code,1,1) = '%' then
            v_tag_code := substr(v_tag_code,2);
            v_output := v_output||v_tag_code;
          elsif v_escape_fn is null then
            v_output := v_output||v_tag_code;
          else
            v_output := v_output||replace(v_escape_fn,':0',v_tag_code);
          end if;
        end if;

        v_startpos := v_tag_end_pos+2;
      end loop;

      return replace(v_generator,':1',v_output);
    end;
    -- -------------------------------------------------------------------------
    procedure apply_to(i_code in out nocopy varchar2)
    is
      v_lines         m4o_v2tab;
      v_has_linestart boolean;
      v_in_embedded   boolean;
      v_embedded_buf  m4o_v2.t;
      v_output        m4o_v2.t;
    begin
      v_lines       := m4o_v2.split(i_code,chr(10));
      v_in_embedded := false;

      for i in 1 .. v_lines.count loop
        v_has_linestart := regexp_like(v_lines(i),v_linestart_pattern);
        v_pc.log('###########################apply_to['||i||']='||v_lines(i));
        v_pc.log('has_ls='||case when v_has_linestart then 'TRUE' else 'FALSE' end);

        if not v_in_embedded and v_has_linestart then
          --
          -- first line with linestart: init
          --
          v_in_embedded := true;
          v_embedded_buf := v_lines(i);
        elsif v_has_linestart then
          --
          -- another line with linestart: append to embedded buf
          --
          v_embedded_buf := v_embedded_buf||chr(10)||v_lines(i);
        elsif v_in_embedded then
          --
          -- no linestart, but embedded up to here: append embedded buf to
          -- v_output
          --
          v_output := v_output||un_embed(v_embedded_buf)||chr(10)||v_lines(i)||chr(10);
          v_in_embedded := false;
        else
          --
          -- just another non-embedded line
          --
          if length(v_output) > 0 then
            v_output := v_output||chr(10)||v_lines(i)||chr(10);
          else
            v_output := v_lines(i)||chr(10);
          end if;
        end if;
      end loop;

      if v_in_embedded then
        v_output := v_output||un_embed(v_embedded_buf);
      end if;

      i_code := v_output;
    end;
    -- -------------------------------------------------------------------------
    procedure check_attributes(
      i_attr in m4o_package_generator.attribute_tt)
    is
    begin
      if i_attr.exists(C_V2_OUTPUT_ATTR) then
        v_generator := nvl(v_generator,
                           replace(
                             C_V2_GENERATOR,
                             ':0',
                             i_attr(C_V2_OUTPUT_ATTR)));
      elsif i_attr.exists(C_CLOB_OUTPUT_ATTR) then
        v_generator := nvl(v_generator,
                           replace(
                             C_CLOB_GENERATOR,
                             ':0',
                             i_attr(C_CLOB_OUTPUT_ATTR)));
      elsif i_attr.exists(C_HTP_OUTPUT_ATTR) then
        v_generator := nvl(v_generator,
                           C_HTP_GENERATOR);
        v_escape_fn := C_HTP_ESCAPE_FN;
      elsif i_attr.exists(C_DBMS_OUTPUT_ATTR) then
        v_generator := nvl(v_generator,
                           C_DBMS_GENERATOR);
      elsif i_attr.exists(C_BUFFER_OUTPUT_ATTR) then
        v_generator := nvl(v_generator,
                           replace(
                             C_BUFFER_GENERATOR,
                             ':0',
                             i_attr(C_BUFFER_OUTPUT_ATTR)));
      end if;

      if i_attr.exists(C_ESCAPE_FN_ATTR) then
        v_escape_fn := i_attr(C_ESCAPE_FN_ATTR);
      end if;
    end;
    -- -------------------------------------------------------------------------
  begin
    if i_part.attributes.exists(C_DISABLE_ATTR) then
      v_pc.log('out is disabled');
    else
      v_generator := m4o_package_generator.get_attribute(C_GENERATOR_ATTR);
      v_linestart_pattern := m4o_package_generator.get_attribute(C_LINESTART_ATTR);

      check_attributes(i_part.attributes);
      if v_generator is null then
        check_attributes(m4o_package_generator.p_package.attributes);
      end if;

      if v_generator is null then
        v_pc.log('no generator - out does not apply');
      else
        v_generator := rtrim(v_generator,m4o_v2.WS||';')||';';

        if v_linestart_pattern is null then
          v_linestart_pattern := C_DEFAULT_LINESTART;
        end if;

        v_pc.log('Generator: "'||
                 v_generator||
                 '", Linestart-pattern="'||
                 v_linestart_pattern||
                 '"');

        apply_to(i_part.sourcecode);

        v_pc.do('set '||m4o_stringliteral_advice.C_DISABLE_ATTR);
        i_part.attributes(m4o_stringliteral_advice.C_DISABLE_ATTR) := null;
      end if;
    end if;
  end;

end;
/


